using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;

using System.IO;
using System.Threading;
using System.IO.Compression;

using System.Runtime.Remoting;
using System.Runtime.Remoting.Services;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Lifetime;

namespace RemoteObject
{

    [Serializable]
    public struct JobInfo
    {
        public double progress;
        public double transferSpeed;
    }

    [Serializable]
    public class TransferParam
    {
        public int BlockSize = Common.BlockSize;
        public Boolean UseCompressMode = false;
    }

    public class DownloadClient
    {
        public TransferParam param = new TransferParam();

        FileStream fs;
        Common.BlockBufferInfo bbiNewBlock;
        Common.BlockBufferInfo bbiToSend;

        int dataLengthBeforeCompression;

        long clientID;
        int startPos, remainReadDataLength, remainSendDataLength;

        bool clientDisposed = false;
        bool readFileError = false;

        AutoResetEvent readNextBlockEvent;

        public void StartDownload(String fn, int offset, int length, long _clientID)
        {
            clientID = _clientID;
            startPos = offset;
            remainReadDataLength = remainSendDataLength = length;

            fs = new FileStream(fn, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
            fs.Seek(startPos, SeekOrigin.Begin);

            bbiToSend = Common.getFreeBlockOnWait(0);
            bbiNewBlock = Common.getFreeBlockOnWait(0);

            readNextBlockEvent = new AutoResetEvent(true);
            Thread thread = new Thread(new ThreadStart(this.ReadFileBlockProc));
            thread.Start();
        }

        Boolean isBufferFree()
        {
            return bbiNewBlock.dataSize == 0;
        }

        void FreeBuffer()
        {
            bbiNewBlock.dataSize = 0;
        }


        private void ReadFileBlockProc()
        {
            while (true)
            {
                //wait GetNextBlock's notification for next block reading.
                double timeout_in_minutes = 15;
                if (false == readNextBlockEvent.WaitOne((int)(1000 * 60 * timeout_in_minutes)))
                {
                    //no response for nMinutes, then kill this client
                    Release();
                    break;
                }

                //read ONE block data to buff
                lock (this)
                {
                    if ( clientDisposed || fs == null || remainReadDataLength <= 0)
                        break;

                    {
                        bbiNewBlock.dataSize = Math.Min(remainReadDataLength, Math.Min(param.BlockSize, Common.BlockSize) );
                        int readLength = fs.Read(bbiNewBlock.buffer, bbiNewBlock.bufferStartPos, bbiNewBlock.dataSize);
                        if (readLength != bbiNewBlock.dataSize)
                        {
                            readFileError = true;
                            Release();
                            break;
                        }

                        remainReadDataLength -= readLength;
                        dataLengthBeforeCompression = -1;
                        if (param.UseCompressMode)
                        {
                            //compute the compress rate
                            Byte[] compressedBlock = MyUtility.CompressionRange(bbiNewBlock.buffer, bbiNewBlock.bufferStartPos, bbiNewBlock.dataSize );
                            if (1.0 * compressedBlock.Length / bbiNewBlock.dataSize > 0.95 )
                            {
                                //ignore the compressed data
                            }
                            else
                            {
                                dataLengthBeforeCompression = readLength;
                                bbiNewBlock.dataSize = compressedBlock.Length;
                                Buffer.BlockCopy( compressedBlock, 0, bbiNewBlock.buffer, bbiNewBlock.bufferStartPos, bbiNewBlock.dataSize );
                            }
                        }
                    }
                    if (remainReadDataLength <= 0)
                        break;
                }
            }
        }

        public void Release()
        {
            if (clientDisposed)
                return;

            try
            {
                clientDisposed = true;
                readNextBlockEvent.Set(); //ask WriteFileProc to quit
                lock (this)
                {
                    Common.releaseBlock(this.bbiNewBlock);
                    Common.releaseBlock(this.bbiToSend);
                    if (fs != null)
                    {
                        fs.Close();
                        fs = null;
                    }
                }
                RemoteObjectDownloader.RemoveClient(clientID);
                System.GC.Collect();
            }
            catch (Exception e)
            {
                Console.WriteLine(e);
            }
        }

        public Byte[] GetNextBlock( out Boolean compressed)
        {
            compressed = false;

            if (remainSendDataLength <= 0)
                return null;

            //Slowdown the speed
            Thread.Sleep(300);

            //waiting for blockBuffer to be filled
            int waitTimes = 0;
            while ( isBufferFree() && !readFileError)
            {
                waitTimes++;
                Thread.Sleep(10);
                if (waitTimes > 100 * 60)
                {
                    //read file time out and close this client connection.
                    Release();
                    return null;
                }
            }

            if (readFileError)
                return null;


            //take the blockBuffer and set it NULL
            int newBlockDataLength = bbiNewBlock.dataSize;
            Byte[] blockToSend = newBlockDataLength == Common.BlockSize ? bbiToSend.buffer : new Byte[newBlockDataLength];
            Buffer.BlockCopy(bbiNewBlock.buffer, bbiNewBlock.bufferStartPos, blockToSend, 0, newBlockDataLength);

            lock (this)
            {
                if (dataLengthBeforeCompression != -1)
                {
                    //compressed data
                    remainSendDataLength -= dataLengthBeforeCompression;
                    compressed = true;
                }
                else
                {
                    remainSendDataLength -= newBlockDataLength;
                }

                //let ReadFile thread to fill the blockBuffer
                FreeBuffer();
                readNextBlockEvent.Set();
            }

            return blockToSend;
        }

    }

    public class MyUtility
    {
        public static byte[] DeCompression(byte[] data, int startPos, int length)
        {
            return DeCompressionRange(data, startPos, length );
        }

        public static byte[] DeCompressionRange(byte[] data, int startPos, int length)
        {
            GZipStream zip = null;
            try
            {
                {
                    MemoryStream ms = new MemoryStream();
                    ms.Write(data, startPos, length);
                    ms.Flush();
                    ms.Position = 0;
                    zip = new GZipStream(ms, CompressionMode.Decompress, true);
                    MemoryStream os = new MemoryStream();
                    int SIZE = 1024;
                    byte[] buf = new byte[SIZE];
                    int l = 0;
                    do
                    {
                        l = zip.Read(buf, 0, SIZE);
                        if (l == 0) l = zip.Read(buf, 0, SIZE);
                        os.Write(buf, 0, l);
                    }
                    while (l != 0);
                    zip.Close();
                    return os.ToArray();
                }
            }
            catch
            {
                if (zip != null) zip.Close();
                return null;
            }
            finally
            {
                if (zip != null) zip.Close();
            }
        }

        public static byte[] CompressionRange(byte[] data, int startPos, int length )
        {
            GZipStream zip = null;
            try
            {
                MemoryStream ms = new MemoryStream();
                zip = new GZipStream(ms, CompressionMode.Compress, true);
                zip.Write(data, startPos, length);
                zip.Close();
                return ms.ToArray();
            }
            catch
            {
                if (zip != null) zip.Close();
                return null;
            }
            finally
            {
                if (zip != null) zip.Close();
            }
        }

        public static byte[] Compression(byte[] data, CompressionMode mode)
        {
            GZipStream zip = null;
            try
            {
                if (mode == CompressionMode.Compress)
                {
                    MemoryStream ms = new MemoryStream();
                    zip = new GZipStream(ms, mode, true);
                    zip.Write(data, 0, data.Length);
                    zip.Close();
                    return ms.ToArray();
                }
                else
                {
                    MemoryStream ms = new MemoryStream();
                    ms.Write(data, 0, data.Length);
                    ms.Flush();
                    ms.Position = 0;
                    zip = new GZipStream(ms, mode, true);
                    MemoryStream os = new MemoryStream();
                    int SIZE = 1024;
                    byte[] buf = new byte[SIZE];
                    int l = 0;
                    do
                    {
                        l = zip.Read(buf, 0, SIZE);
                        if (l == 0) l = zip.Read(buf, 0, SIZE);
                        os.Write(buf, 0, l);
                    }
                    while (l != 0);
                    zip.Close();
                    return os.ToArray();
                }
            }
            catch
            {
                if (zip != null) zip.Close();
                return null;
            }
            finally
            {
                if (zip != null) zip.Close();
            }
        }
    }

    public class RemoteObjectDownloader : BaseRemoteObject
    {
        public delegate void MessageHandler(string fnRemote, string fnLocal, string ip, long clientID);
        public static event MessageHandler OnUploadRequest;
        public static event MessageHandler OnDistributeRequest;

        public delegate void MessageHandler2 (long clientID);
        public static event MessageHandler2 OnStopUploadRequest;

        public delegate JobInfo MessageHandler3(long clientID);
        public static event MessageHandler3 OnSyncRequest;

        public delegate List<DownloadJobTicket> MessageHandler4();
        public static event MessageHandler4 OnJobDistributionRequest;

        public delegate DownloadJob MessageHandler5(string jobName);
        public static event MessageHandler5 OnDistributedJobRequest;

        public DownloadJob RequestDistributedJob(string jobName)
        {
            if (OnDistributedJobRequest != null)
                return OnDistributedJobRequest(jobName);
            return null;
        }


        public void StartUploadRequest(string remoteFile, string localFile, string serviceURLTcp, long jobID)
        {
            if (OnUploadRequest != null)
                OnUploadRequest(remoteFile, localFile, serviceURLTcp, jobID);
        }

        public void DistributeRequest(string remoteFile, string localFile, string serviceURLTcp, long jobID)
        {
            if (OnDistributeRequest != null)
                OnDistributeRequest(remoteFile, localFile, serviceURLTcp, jobID);
        }

        public void StopUploadRequest(long jobID)
        {
            if (OnStopUploadRequest != null)
                OnStopUploadRequest(jobID);
        }

        public JobInfo SyncRequest(long jobID)
        {
            if (OnSyncRequest != null)
                return OnSyncRequest(jobID);
            return new JobInfo();
        }

        public List<DownloadJobTicket> GetFileDistributionRequest()
        {
            if (OnJobDistributionRequest != null)
                return OnJobDistributionRequest();
            return new List<DownloadJobTicket>();
        }

        //------------------------------------------------------------------------------------------------
        public static long clientID = 0;
        public static Hashtable downClientList = new Hashtable();

        public long GetNewClientID()
        {
            lock (downClientList)
            {
                return ++clientID;
            }
        }

        static public void RemoveClient(long clientID)
        {
            downClientList.Remove(clientID);
        }

        public long GetFileLength(String _fn)
        {
            String fn = Common.LocalPath + _fn;
            if (!File.Exists(fn))
                return -1;

            FileInfo fileInfo = new FileInfo(fn);
            return fileInfo.Length;
        }

        public long StartDownloadFile(String _fn, long clientID, int startPos, int dataLength, TransferParam param )
        {
            String fn = Common.LocalPath + _fn;
            if (!File.Exists(fn))
                return -1;

            if (downClientList.Contains(clientID))
            {
                DownloadClient ci = (DownloadClient)downClientList[clientID];
                ci.param = param;
                ci.StartDownload(fn, startPos, dataLength, clientID);
            }
            else
            {
                //create new Client and put it in gloal hashtable.
                DownloadClient newCI = new DownloadClient();
                newCI.param = param;
                newCI.StartDownload(fn, startPos, dataLength, clientID);
                downClientList.Add(clientID, newCI);
            }

            FileInfo fileInfo = new FileInfo(fn);
            return fileInfo.Length;
        }

        public int StopDownloadFile(long clientID)
        {
            if (!downClientList.Contains(clientID))
                return -1;

            DownloadClient ci = (DownloadClient)downClientList[clientID];
            ci.Release();

            return 0;
        }

        public Byte[] RecevingFileData(long clientID, out Boolean compressed )
        {
            DownloadClient ci;
            Byte[] block = null;
            compressed = false;
            if (downClientList.Contains(clientID))
            {
                ci = (DownloadClient)downClientList[clientID];
                block = ci.GetNextBlock( out compressed);
            }
            return block;
        }

        public Byte HeartBeatObserver()
        {
            return 1;
        }
    }

    public class MyTracking : ITrackingHandler
    {
        public MyTracking()
        {
        }

        public void MarshaledObject(Object obj, ObjRef objRef)
        {
            // Notify the user of the marshal event.
            Console.WriteLine("Object " + obj.ToString() + " is marshaled at " + DateTime.Now.ToLongTimeString());

            // Print the channel information.
            if (objRef.ChannelInfo != null)
            {
                // Iterate over ChannelData.
                foreach (object data in objRef.ChannelInfo.ChannelData)
                {
                    if (data is ChannelDataStore)
                    {
                        // Print the URIs from the ChannelDataStore objects.
                        string[] uris = ((ChannelDataStore)data).ChannelUris;
                        foreach (string uri in uris)
                            Console.WriteLine("ChannelUri: " + uri);
                    }
                }
            }

            /*
            // Print the envoy information.
            if (objRef.EnvoyInfo != null)
                Console.WriteLine("EnvoyInfo: " + objRef.EnvoyInfo.ToString());

            // Print the type information.
            if (objRef.TypeInfo != null)
            {
                Console.WriteLine("TypeInfo: " + objRef.TypeInfo.ToString());
                Console.WriteLine("TypeName: " + objRef.TypeInfo.TypeName);
            }

            // Print the URI.
            if (objRef.URI != null)
                Console.WriteLine("URI: " + objRef.URI.ToString());
             */
        }

        public void UnmarshaledObject(object obj, ObjRef objRef)
        {
            Console.WriteLine();
            Console.WriteLine("Object" + obj.ToString() + " is unmarshaled at " + DateTime.Now.ToLongTimeString());
        }

        public void DisconnectedObject(object obj)
        {
            Console.WriteLine(obj.ToString() + " is disconnected at " + DateTime.Now.ToLongTimeString());
        }
    }

    public class BaseRemoteObject : MarshalByRefObject
    {
        public override object InitializeLifetimeService()
        {
            ILease lease = (ILease)base.InitializeLifetimeService();
            if (lease.CurrentState == LeaseState.Initial)
            {
                lease.InitialLeaseTime = TimeSpan.FromSeconds(30);
                lease.RenewOnCallTime = TimeSpan.FromSeconds(30);
            }
            return lease;

        }
    }
}
